/*
 * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.nio;

import java.security.AccessController;
import sun.misc.Unsafe;
import sun.misc.VM;

/**
 * Access to bits, native and otherwise.
 */

class Bits {                            // package-private

  private Bits() {
  }

  // -- Swapping --

  static short swap(short x) {
    return Short.reverseBytes(x);
  }

  static char swap(char x) {
    return Character.reverseBytes(x);
  }

  static int swap(int x) {
    return Integer.reverseBytes(x);
  }

  static long swap(long x) {
    return Long.reverseBytes(x);
  }

  // -- get/put char --

  static private char makeChar(byte b1, byte b0) {
    return (char) ((b1 << 8) | (b0 & 0xff));
  }

  static char getCharL(ByteBuffer bb, int bi) {
    return makeChar(bb._get(bi + 1),
        bb._get(bi));
  }

  static char getCharL(long a) {
    return makeChar(_get(a + 1),
        _get(a));
  }

  static char getCharB(ByteBuffer bb, int bi) {
    return makeChar(bb._get(bi),
        bb._get(bi + 1));
  }

  static char getCharB(long a) {
    return makeChar(_get(a),
        _get(a + 1));
  }

  static char getChar(ByteBuffer bb, int bi, boolean bigEndian) {
    return bigEndian ? getCharB(bb, bi) : getCharL(bb, bi);
  }

  static char getChar(long a, boolean bigEndian) {
    return bigEndian ? getCharB(a) : getCharL(a);
  }

  private static byte char1(char x) {
    return (byte) (x >> 8);
  }

  private static byte char0(char x) {
    return (byte) (x);
  }

  static void putCharL(ByteBuffer bb, int bi, char x) {
    bb._put(bi, char0(x));
    bb._put(bi + 1, char1(x));
  }

  static void putCharL(long a, char x) {
    _put(a, char0(x));
    _put(a + 1, char1(x));
  }

  static void putCharB(ByteBuffer bb, int bi, char x) {
    bb._put(bi, char1(x));
    bb._put(bi + 1, char0(x));
  }

  static void putCharB(long a, char x) {
    _put(a, char1(x));
    _put(a + 1, char0(x));
  }

  static void putChar(ByteBuffer bb, int bi, char x, boolean bigEndian) {
    if (bigEndian) {
      putCharB(bb, bi, x);
    } else {
      putCharL(bb, bi, x);
    }
  }

  static void putChar(long a, char x, boolean bigEndian) {
    if (bigEndian) {
      putCharB(a, x);
    } else {
      putCharL(a, x);
    }
  }

  // -- get/put short --

  static private short makeShort(byte b1, byte b0) {
    return (short) ((b1 << 8) | (b0 & 0xff));
  }

  static short getShortL(ByteBuffer bb, int bi) {
    return makeShort(bb._get(bi + 1),
        bb._get(bi));
  }

  static short getShortL(long a) {
    return makeShort(_get(a + 1),
        _get(a));
  }

  static short getShortB(ByteBuffer bb, int bi) {
    return makeShort(bb._get(bi),
        bb._get(bi + 1));
  }

  static short getShortB(long a) {
    return makeShort(_get(a),
        _get(a + 1));
  }

  static short getShort(ByteBuffer bb, int bi, boolean bigEndian) {
    return bigEndian ? getShortB(bb, bi) : getShortL(bb, bi);
  }

  static short getShort(long a, boolean bigEndian) {
    return bigEndian ? getShortB(a) : getShortL(a);
  }

  private static byte short1(short x) {
    return (byte) (x >> 8);
  }

  private static byte short0(short x) {
    return (byte) (x);
  }

  static void putShortL(ByteBuffer bb, int bi, short x) {
    bb._put(bi, short0(x));
    bb._put(bi + 1, short1(x));
  }

  static void putShortL(long a, short x) {
    _put(a, short0(x));
    _put(a + 1, short1(x));
  }

  static void putShortB(ByteBuffer bb, int bi, short x) {
    bb._put(bi, short1(x));
    bb._put(bi + 1, short0(x));
  }

  static void putShortB(long a, short x) {
    _put(a, short1(x));
    _put(a + 1, short0(x));
  }

  static void putShort(ByteBuffer bb, int bi, short x, boolean bigEndian) {
    if (bigEndian) {
      putShortB(bb, bi, x);
    } else {
      putShortL(bb, bi, x);
    }
  }

  static void putShort(long a, short x, boolean bigEndian) {
    if (bigEndian) {
      putShortB(a, x);
    } else {
      putShortL(a, x);
    }
  }

  // -- get/put int --

  static private int makeInt(byte b3, byte b2, byte b1, byte b0) {
    return (((b3) << 24) |
        ((b2 & 0xff) << 16) |
        ((b1 & 0xff) << 8) |
        ((b0 & 0xff)));
  }

  static int getIntL(ByteBuffer bb, int bi) {
    return makeInt(bb._get(bi + 3),
        bb._get(bi + 2),
        bb._get(bi + 1),
        bb._get(bi));
  }

  static int getIntL(long a) {
    return makeInt(_get(a + 3),
        _get(a + 2),
        _get(a + 1),
        _get(a));
  }

  static int getIntB(ByteBuffer bb, int bi) {
    return makeInt(bb._get(bi),
        bb._get(bi + 1),
        bb._get(bi + 2),
        bb._get(bi + 3));
  }

  static int getIntB(long a) {
    return makeInt(_get(a),
        _get(a + 1),
        _get(a + 2),
        _get(a + 3));
  }

  static int getInt(ByteBuffer bb, int bi, boolean bigEndian) {
    return bigEndian ? getIntB(bb, bi) : getIntL(bb, bi);
  }

  static int getInt(long a, boolean bigEndian) {
    return bigEndian ? getIntB(a) : getIntL(a);
  }

  private static byte int3(int x) {
    return (byte) (x >> 24);
  }

  private static byte int2(int x) {
    return (byte) (x >> 16);
  }

  private static byte int1(int x) {
    return (byte) (x >> 8);
  }

  private static byte int0(int x) {
    return (byte) (x);
  }

  static void putIntL(ByteBuffer bb, int bi, int x) {
    bb._put(bi + 3, int3(x));
    bb._put(bi + 2, int2(x));
    bb._put(bi + 1, int1(x));
    bb._put(bi, int0(x));
  }

  static void putIntL(long a, int x) {
    _put(a + 3, int3(x));
    _put(a + 2, int2(x));
    _put(a + 1, int1(x));
    _put(a, int0(x));
  }

  static void putIntB(ByteBuffer bb, int bi, int x) {
    bb._put(bi, int3(x));
    bb._put(bi + 1, int2(x));
    bb._put(bi + 2, int1(x));
    bb._put(bi + 3, int0(x));
  }

  static void putIntB(long a, int x) {
    _put(a, int3(x));
    _put(a + 1, int2(x));
    _put(a + 2, int1(x));
    _put(a + 3, int0(x));
  }

  static void putInt(ByteBuffer bb, int bi, int x, boolean bigEndian) {
    if (bigEndian) {
      putIntB(bb, bi, x);
    } else {
      putIntL(bb, bi, x);
    }
  }

  static void putInt(long a, int x, boolean bigEndian) {
    if (bigEndian) {
      putIntB(a, x);
    } else {
      putIntL(a, x);
    }
  }

  // -- get/put long --

  static private long makeLong(byte b7, byte b6, byte b5, byte b4,
      byte b3, byte b2, byte b1, byte b0) {
    return ((((long) b7) << 56) |
        (((long) b6 & 0xff) << 48) |
        (((long) b5 & 0xff) << 40) |
        (((long) b4 & 0xff) << 32) |
        (((long) b3 & 0xff) << 24) |
        (((long) b2 & 0xff) << 16) |
        (((long) b1 & 0xff) << 8) |
        (((long) b0 & 0xff)));
  }

  static long getLongL(ByteBuffer bb, int bi) {
    return makeLong(bb._get(bi + 7),
        bb._get(bi + 6),
        bb._get(bi + 5),
        bb._get(bi + 4),
        bb._get(bi + 3),
        bb._get(bi + 2),
        bb._get(bi + 1),
        bb._get(bi));
  }

  static long getLongL(long a) {
    return makeLong(_get(a + 7),
        _get(a + 6),
        _get(a + 5),
        _get(a + 4),
        _get(a + 3),
        _get(a + 2),
        _get(a + 1),
        _get(a));
  }

  static long getLongB(ByteBuffer bb, int bi) {
    return makeLong(bb._get(bi),
        bb._get(bi + 1),
        bb._get(bi + 2),
        bb._get(bi + 3),
        bb._get(bi + 4),
        bb._get(bi + 5),
        bb._get(bi + 6),
        bb._get(bi + 7));
  }

  static long getLongB(long a) {
    return makeLong(_get(a),
        _get(a + 1),
        _get(a + 2),
        _get(a + 3),
        _get(a + 4),
        _get(a + 5),
        _get(a + 6),
        _get(a + 7));
  }

  static long getLong(ByteBuffer bb, int bi, boolean bigEndian) {
    return bigEndian ? getLongB(bb, bi) : getLongL(bb, bi);
  }

  static long getLong(long a, boolean bigEndian) {
    return bigEndian ? getLongB(a) : getLongL(a);
  }

  private static byte long7(long x) {
    return (byte) (x >> 56);
  }

  private static byte long6(long x) {
    return (byte) (x >> 48);
  }

  private static byte long5(long x) {
    return (byte) (x >> 40);
  }

  private static byte long4(long x) {
    return (byte) (x >> 32);
  }

  private static byte long3(long x) {
    return (byte) (x >> 24);
  }

  private static byte long2(long x) {
    return (byte) (x >> 16);
  }

  private static byte long1(long x) {
    return (byte) (x >> 8);
  }

  private static byte long0(long x) {
    return (byte) (x);
  }

  static void putLongL(ByteBuffer bb, int bi, long x) {
    bb._put(bi + 7, long7(x));
    bb._put(bi + 6, long6(x));
    bb._put(bi + 5, long5(x));
    bb._put(bi + 4, long4(x));
    bb._put(bi + 3, long3(x));
    bb._put(bi + 2, long2(x));
    bb._put(bi + 1, long1(x));
    bb._put(bi, long0(x));
  }

  static void putLongL(long a, long x) {
    _put(a + 7, long7(x));
    _put(a + 6, long6(x));
    _put(a + 5, long5(x));
    _put(a + 4, long4(x));
    _put(a + 3, long3(x));
    _put(a + 2, long2(x));
    _put(a + 1, long1(x));
    _put(a, long0(x));
  }

  static void putLongB(ByteBuffer bb, int bi, long x) {
    bb._put(bi, long7(x));
    bb._put(bi + 1, long6(x));
    bb._put(bi + 2, long5(x));
    bb._put(bi + 3, long4(x));
    bb._put(bi + 4, long3(x));
    bb._put(bi + 5, long2(x));
    bb._put(bi + 6, long1(x));
    bb._put(bi + 7, long0(x));
  }

  static void putLongB(long a, long x) {
    _put(a, long7(x));
    _put(a + 1, long6(x));
    _put(a + 2, long5(x));
    _put(a + 3, long4(x));
    _put(a + 4, long3(x));
    _put(a + 5, long2(x));
    _put(a + 6, long1(x));
    _put(a + 7, long0(x));
  }

  static void putLong(ByteBuffer bb, int bi, long x, boolean bigEndian) {
    if (bigEndian) {
      putLongB(bb, bi, x);
    } else {
      putLongL(bb, bi, x);
    }
  }

  static void putLong(long a, long x, boolean bigEndian) {
    if (bigEndian) {
      putLongB(a, x);
    } else {
      putLongL(a, x);
    }
  }

  // -- get/put float --

  static float getFloatL(ByteBuffer bb, int bi) {
    return Float.intBitsToFloat(getIntL(bb, bi));
  }

  static float getFloatL(long a) {
    return Float.intBitsToFloat(getIntL(a));
  }

  static float getFloatB(ByteBuffer bb, int bi) {
    return Float.intBitsToFloat(getIntB(bb, bi));
  }

  static float getFloatB(long a) {
    return Float.intBitsToFloat(getIntB(a));
  }

  static float getFloat(ByteBuffer bb, int bi, boolean bigEndian) {
    return bigEndian ? getFloatB(bb, bi) : getFloatL(bb, bi);
  }

  static float getFloat(long a, boolean bigEndian) {
    return bigEndian ? getFloatB(a) : getFloatL(a);
  }

  static void putFloatL(ByteBuffer bb, int bi, float x) {
    putIntL(bb, bi, Float.floatToRawIntBits(x));
  }

  static void putFloatL(long a, float x) {
    putIntL(a, Float.floatToRawIntBits(x));
  }

  static void putFloatB(ByteBuffer bb, int bi, float x) {
    putIntB(bb, bi, Float.floatToRawIntBits(x));
  }

  static void putFloatB(long a, float x) {
    putIntB(a, Float.floatToRawIntBits(x));
  }

  static void putFloat(ByteBuffer bb, int bi, float x, boolean bigEndian) {
    if (bigEndian) {
      putFloatB(bb, bi, x);
    } else {
      putFloatL(bb, bi, x);
    }
  }

  static void putFloat(long a, float x, boolean bigEndian) {
    if (bigEndian) {
      putFloatB(a, x);
    } else {
      putFloatL(a, x);
    }
  }

  // -- get/put double --

  static double getDoubleL(ByteBuffer bb, int bi) {
    return Double.longBitsToDouble(getLongL(bb, bi));
  }

  static double getDoubleL(long a) {
    return Double.longBitsToDouble(getLongL(a));
  }

  static double getDoubleB(ByteBuffer bb, int bi) {
    return Double.longBitsToDouble(getLongB(bb, bi));
  }

  static double getDoubleB(long a) {
    return Double.longBitsToDouble(getLongB(a));
  }

  static double getDouble(ByteBuffer bb, int bi, boolean bigEndian) {
    return bigEndian ? getDoubleB(bb, bi) : getDoubleL(bb, bi);
  }

  static double getDouble(long a, boolean bigEndian) {
    return bigEndian ? getDoubleB(a) : getDoubleL(a);
  }

  static void putDoubleL(ByteBuffer bb, int bi, double x) {
    putLongL(bb, bi, Double.doubleToRawLongBits(x));
  }

  static void putDoubleL(long a, double x) {
    putLongL(a, Double.doubleToRawLongBits(x));
  }

  static void putDoubleB(ByteBuffer bb, int bi, double x) {
    putLongB(bb, bi, Double.doubleToRawLongBits(x));
  }

  static void putDoubleB(long a, double x) {
    putLongB(a, Double.doubleToRawLongBits(x));
  }

  static void putDouble(ByteBuffer bb, int bi, double x, boolean bigEndian) {
    if (bigEndian) {
      putDoubleB(bb, bi, x);
    } else {
      putDoubleL(bb, bi, x);
    }
  }

  static void putDouble(long a, double x, boolean bigEndian) {
    if (bigEndian) {
      putDoubleB(a, x);
    } else {
      putDoubleL(a, x);
    }
  }

  // -- Unsafe access --

  private static final Unsafe unsafe = Unsafe.getUnsafe();

  private static byte _get(long a) {
    return unsafe.getByte(a);
  }

  private static void _put(long a, byte b) {
    unsafe.putByte(a, b);
  }

  static Unsafe unsafe() {
    return unsafe;
  }

  // -- Processor and memory-system properties --

  private static final ByteOrder byteOrder;

  static ByteOrder byteOrder() {
    if (byteOrder == null) {
      throw new Error("Unknown byte order");
    }
    return byteOrder;
  }

  static {
    long a = unsafe.allocateMemory(8);
    try {
      unsafe.putLong(a, 0x0102030405060708L);
      byte b = unsafe.getByte(a);
      switch (b) {
        case 0x01:
          byteOrder = ByteOrder.BIG_ENDIAN;
          break;
        case 0x08:
          byteOrder = ByteOrder.LITTLE_ENDIAN;
          break;
        default:
          assert false;
          byteOrder = null;
      }
    } finally {
      unsafe.freeMemory(a);
    }
  }


  private static int pageSize = -1;

  static int pageSize() {
    if (pageSize == -1) {
      pageSize = unsafe().pageSize();
    }
    return pageSize;
  }

  static int pageCount(long size) {
    return (int) (size + (long) pageSize() - 1L) / pageSize();
  }

  private static boolean unaligned;
  private static boolean unalignedKnown = false;

  static boolean unaligned() {
    if (unalignedKnown) {
      return unaligned;
    }
    String arch = AccessController.doPrivileged(
        new sun.security.action.GetPropertyAction("os.arch"));
    unaligned = arch.equals("i386") || arch.equals("x86")
        || arch.equals("amd64") || arch.equals("x86_64");
    unalignedKnown = true;
    return unaligned;
  }

  // -- Direct memory management --

  // A user-settable upper limit on the maximum amount of allocatable
  // direct buffer memory.  This value may be changed during VM
  // initialization if it is launched with "-XX:MaxDirectMemorySize=<size>".
  private static volatile long maxMemory = VM.maxDirectMemory();
  private static volatile long reservedMemory;
  private static volatile long totalCapacity;
  private static volatile long count;
  private static boolean memoryLimitSet = false;

  // These methods should be called whenever direct memory is allocated or
  // freed.  They allow the user to control the amount of direct memory
  // which a process may access.  All sizes are specified in bytes.
  static void reserveMemory(long size, int cap) {
    synchronized (Bits.class) {
      if (!memoryLimitSet && VM.isBooted()) {
        maxMemory = VM.maxDirectMemory();
        memoryLimitSet = true;
      }
      // -XX:MaxDirectMemorySize limits the total capacity rather than the
      // actual memory usage, which will differ when buffers are page
      // aligned.
      if (cap <= maxMemory - totalCapacity) {
        reservedMemory += size;
        totalCapacity += cap;
        count++;
        return;
      }
    }

    System.gc();
    try {
      Thread.sleep(100);
    } catch (InterruptedException x) {
      // Restore interrupt status
      Thread.currentThread().interrupt();
    }
    synchronized (Bits.class) {
      if (totalCapacity + cap > maxMemory) {
        throw new OutOfMemoryError("Direct buffer memory");
      }
      reservedMemory += size;
      totalCapacity += cap;
      count++;
    }

  }

  static synchronized void unreserveMemory(long size, int cap) {
    if (reservedMemory > 0) {
      reservedMemory -= size;
      totalCapacity -= cap;
      count--;
      assert (reservedMemory > -1);
    }
  }

  // -- Monitoring of direct buffer usage --

  static {
    // setup access to this package in SharedSecrets
    sun.misc.SharedSecrets.setJavaNioAccess(
        new sun.misc.JavaNioAccess() {
          @Override
          public sun.misc.JavaNioAccess.BufferPool getDirectBufferPool() {
            return new sun.misc.JavaNioAccess.BufferPool() {
              @Override
              public String getName() {
                return "direct";
              }

              @Override
              public long getCount() {
                return Bits.count;
              }

              @Override
              public long getTotalCapacity() {
                return Bits.totalCapacity;
              }

              @Override
              public long getMemoryUsed() {
                return Bits.reservedMemory;
              }
            };
          }

          @Override
          public ByteBuffer newDirectByteBuffer(long addr, int cap, Object ob) {
            return new DirectByteBuffer(addr, cap, ob);
          }

          @Override
          public void truncate(Buffer buf) {
            buf.truncate();
          }
        });
  }

  // -- Bulk get/put acceleration --

  // These numbers represent the point at which we have empirically
  // determined that the average cost of a JNI call exceeds the expense
  // of an element by element copy.  These numbers may change over time.
  static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6;
  static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6;

  // This number limits the number of bytes to copy per call to Unsafe's
  // copyMemory method. A limit is imposed to allow for safepoint polling
  // during a large copy
  static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;

  // These methods do no bounds checking.  Verification that the copy will not
  // result in memory corruption should be done prior to invocation.
  // All positions and lengths are specified in bytes.

  /**
   * Copy from given source array to destination address.
   *
   * @param src source array
   * @param srcBaseOffset offset of first element of storage in source array
   * @param srcPos offset within source array of the first element to read
   * @param dstAddr destination address
   * @param length number of bytes to copy
   */
  static void copyFromArray(Object src, long srcBaseOffset, long srcPos,
      long dstAddr, long length) {
    long offset = srcBaseOffset + srcPos;
    while (length > 0) {
      long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length;
      unsafe.copyMemory(src, offset, null, dstAddr, size);
      length -= size;
      offset += size;
      dstAddr += size;
    }
  }

  /**
   * Copy from source address into given destination array.
   *
   * @param srcAddr source address
   * @param dst destination array
   * @param dstBaseOffset offset of first element of storage in destination array
   * @param dstPos offset within destination array of the first element to write
   * @param length number of bytes to copy
   */
  static void copyToArray(long srcAddr, Object dst, long dstBaseOffset, long dstPos,
      long length) {
    long offset = dstBaseOffset + dstPos;
    while (length > 0) {
      long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length;
      unsafe.copyMemory(null, srcAddr, dst, offset, size);
      length -= size;
      srcAddr += size;
      offset += size;
    }
  }

  static void copyFromCharArray(Object src, long srcPos, long dstAddr,
      long length) {
    copyFromShortArray(src, srcPos, dstAddr, length);
  }

  static void copyToCharArray(long srcAddr, Object dst, long dstPos,
      long length) {
    copyToShortArray(srcAddr, dst, dstPos, length);
  }

  static native void copyFromShortArray(Object src, long srcPos, long dstAddr,
      long length);

  static native void copyToShortArray(long srcAddr, Object dst, long dstPos,
      long length);

  static native void copyFromIntArray(Object src, long srcPos, long dstAddr,
      long length);

  static native void copyToIntArray(long srcAddr, Object dst, long dstPos,
      long length);

  static native void copyFromLongArray(Object src, long srcPos, long dstAddr,
      long length);

  static native void copyToLongArray(long srcAddr, Object dst, long dstPos,
      long length);

}
